﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace Eric.SerialPorts
{
    /// <summary>
    /// The assistant class for verify data intergrity.
    /// </summary>
    public abstract class DataVerifier : IDisposable
    {
        #region Private Properties
        private const int DefaultBufferSize = 4096;
        private readonly List<byte> _bufferBytes;
        private int BufferSize
        {
            get
            {
                lock (_bufferBytes)
                {

                    return _bufferBytes.Count;
                }
            }
        }
        private Thread _proccessDataThread;
        private readonly AutoResetEvent _processEvent;
        private readonly AutoResetEvent _exitEvent;
        #endregion

        #region Constructors
        /// <summary>
        /// Constructor of DataVerifier Class.
        /// </summary>
        protected DataVerifier()
        {
            _bufferBytes = new List<byte>(DefaultBufferSize);
            _processEvent = new AutoResetEvent(false);
            _exitEvent = new AutoResetEvent(false);
            _isDisposed = false;
        }

        #endregion

        #region Event and Event Handlers
        public event EventHandler<SerialPortWrapperDataEventArgs> BizCommandReceivedEvent;
        private void RaiseBizeCommandReceived(SerialPortWrapperDataEventArgs e)
        {
            if (BizCommandReceivedEvent != null)
            {
                BizCommandReceivedEvent(this, e);
            }
        }
        #endregion

        public void StartProcessDataThread()
        {
            #region Required
            if (_isDisposed)
            {
                return;
            }
            #endregion
            _proccessDataThread = new Thread(ProcessData);
            _proccessDataThread.Start();
        }
        public void StartProcess()
        {
            #region Required
            if (_isDisposed)
            {
                return;
            }
            #endregion
            _processEvent.Set();
        }
        public void ExitProcess()
        {
            _exitEvent.Set();
        }

        private void ProcessData()
        {
            var waitHandle = new WaitHandle[] { _exitEvent, _processEvent };
            while (true)
            {
                var handleIndex = WaitHandle.WaitAny(waitHandle);
                if (handleIndex == 0)
                    break;
                if (handleIndex != 1) continue;
                _processEvent.Reset();
                ParseRawData();
            }
        }

        private void ParseRawData()
        {
            while (BufferSize > 0)
            {
                #region Required
                if (_isDisposed)
                {
                    break;
                }
                #endregion

                byte[] singleWholeCommand;
                lock (_bufferBytes)
                {
                    singleWholeCommand = PushOneSingleCommandFromBegin(_bufferBytes);
                }
                var singleBizCommand = DecodeBizData(singleWholeCommand);
                if (singleBizCommand == null)
                    continue;
                //Raise an bussiness command received event.
                var arg = new SerialPortWrapperDataEventArgs(singleBizCommand);
                RaiseBizeCommandReceived(arg);
            }
        }

        /// <summary>
        /// Add raw data received from serial port.
        /// </summary>
        /// <param name="rawBytes">Raw data received from serial port.</param>
        public void AddRawData(byte[] rawBytes)
        {
            if (rawBytes == null)
                return;
            lock (_bufferBytes)
            {
                _bufferBytes.AddRange(rawBytes);
            }
        }

        #region Public Virtual Methods

        /// <summary>
        /// Get one single comand and remove bytes that has read.
        /// </summary>
        /// <param name="rawBytes">Raw bytes.</param>
        /// <returns>One single command.</returns>
        public abstract byte[] PushOneSingleCommandFromBegin(IList<byte> rawBytes);
        public abstract byte[] EncodeBizData(byte[] bytesBiz);
        public abstract byte[] DecodeBizData(byte[] bytes);

        #endregion

        private bool _isDisposed;
        public void Dispose()
        {
            _isDisposed = true;
            _bufferBytes.Clear();
            ExitProcess();
        }
    }
}
